﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Citrix.Cmp;
using Citrix.XaAdminMobile.Model;
using Citrix.XaAdminMobile.Util;

namespace Citrix.XaAdminMobile.ViewModel
{
    /// <summary>
    /// The servers v-m object for the servers view (page).
    /// </summary>
    internal class ServersViewModel : INotifyPropertyChanged, IPage
    {
        /// <summary>
        /// Reference to the associated servers model object.
        /// </summary>
        private readonly IServersModel model;

        /// <summary>
        /// A reference back to the root v-m object.
        /// </summary>
        private readonly ViewModelManager vmRoot;

        /// <summary>
        /// Used to sync access to internal state.
        /// </summary>
        private object stateLock = new object();

        /// <summary>
        /// Used to display a loading status.
        /// </summary>
        private LoadingViewModel loadingStatus;

        /// <summary>
        /// Used to display errors to the user.
        /// </summary>
        private ErrorStatusViewModel errorStatus;

        /// <summary>
        /// Holds our master set of server v-m objects, keyed by server name.
        /// </summary>
        private Dictionary<string, ServerViewModel> servers =
            new Dictionary<string, ServerViewModel>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// Holds the list of servers in sorted display order.
        /// </summary>
        private List<ServerViewModel> displayOrderServers = null;

        /// <summary>
        /// The currently selected server.
        /// </summary>
        private ServerViewModel selectedServer = null;

        /// <summary>
        /// Initialisation ctor.
        /// </summary>
        /// <param name="vmMgr">A reference back to the root v-m object.</param>
        /// <param name="source">The associated model object.</param>
        /// <exception cref="ArgumentNullException">Thrown if any argument is null.</exception>
        internal ServersViewModel(ViewModelManager vmMgr, IServersModel source)
        {
            if (null == vmMgr)
            {
                throw new ArgumentNullException("vmMgr");
            }
            if (null == source)
            {
                throw new ArgumentNullException("source");
            }

            this.vmRoot = vmMgr;
            this.model = source;
            this.errorStatus = new ErrorStatusViewModel();
            this.RefreshCommand = new DelegateCommand(() => this.model.Refresh());
            this.loadingStatus = new LoadingViewModel(() => { return this.model.IsLoading; });

            this.model.ServersChanged += new EventHandler<ServersChangedArgs>(ServersChanged);
            this.model.PropertyChanged += new PropertyChangedEventHandler(Model_PropertyChanged);

            // Load any initial server available from the model object.
            if (this.model.Servers.Count > 0)
            {
                AddServers(this.model.Servers);
            }
        }

        /// <summary>
        /// Gets the default server to display the details for when in the tablet layout.
        /// </summary>
        /// <returns>The default server, or null if none is available.</returns>
        public ServerViewModel GetDefaultServer()
        {
            var servers = this.Servers;
            if (servers.Count > 0)
            {
                return servers[0];
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// The error status for loading server state.
        /// </summary>
        public ErrorStatusViewModel ErrorStatus
        {
            get { return this.errorStatus; }
        }

        /// <summary>
        /// Whether or not we are currently loading new server state.
        /// </summary>
        public LoadingViewModel LoadingStatus
        {
            get { return this.loadingStatus; }
        }

        /// <summary>
        /// The master navigation toolbar v-m.
        /// </summary>
        public NavToolbarViewModel NavToolbarViewModel
        {
            get { return this.vmRoot.NavToolbarViewModel; }
        }

        /// <summary>
        /// Refreshes all of the server state.
        /// </summary>
        public DelegateCommand RefreshCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// The currently selected server.
        /// </summary>
        public ServerViewModel SelectedServer
        {
            get
            {
                //
                // We only want the highlight in the tablet layout, so return null for the phone
                // layout. Generally we would create separate views for phone vs tablet, but I
                // chose to put the exception here in the v-m since it was such a minor
                // difference to create two views for.
                //
                if (this.vmRoot.ActiveNavigation is TabletNavigation)
                {
                    return this.selectedServer;
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// The set of known XenApp servers in the farm, ordered by name.
        /// </summary>
        public List<ServerViewModel> Servers
        {
            get
            {
                List<ServerViewModel> result;
                lock (this.stateLock)
                {
                    if (null == this.displayOrderServers)
                    {
                        this.displayOrderServers = this.servers.Values.OrderBy(s => s.Name).ToList();
                    }
                    result = this.displayOrderServers;
                }
                return result;
            }
        }

        /// <summary>
        /// Called by server v-ms to notify us when they become selected so we can display a
        /// selection highlight in the tablet layout.
        /// </summary>
        /// <param name="server">The selected server.</param>
        internal void NotifyServerSelected(ServerViewModel server)
        {
            this.selectedServer = server;
            OnPropertyChanged("SelectedServer");
        }

        /// <summary>
        /// Handler for model properties changing.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsLoading")
            {
                this.vmRoot.UiDispatch(() => this.loadingStatus.NotifyChanged());
            }
            else if (e.PropertyName == "LastLoadError")
            {
                this.vmRoot.UiDispatch(() => this.errorStatus.ErrorMessage = this.model.LastLoadError);
            }
        }

        /// <summary>
        /// Handler for the servers changing. We add/remove server v-m objects to reflect the
        /// changes.
        /// </summary>
        /// <param name="sender">Not used.</param>
        /// <param name="e">Servers changed arguments.</param>
        private void ServersChanged(object sender, ServersChangedArgs e)
        {
            bool changed = false;
            lock (this.stateLock)
            {
                this.displayOrderServers = null;
                if (e.AddedServers.Count > 0)
                {
                    changed = true;
                    AddServers(e.AddedServers);
                }
                if (e.RemovedServers.Count > 0)
                {
                    changed = true;
                    foreach (var s in e.RemovedServers)
                    {
                        this.servers.Remove(s.Name);
                    }
                }
            }

            if (changed)
            {
                this.vmRoot.UiDispatch(() => OnPropertyChanged("Servers"));
            }
        }

        /// <summary>
        /// Creates new server v-m objects for the specified set of server model objects.
        /// </summary>
        /// <param name="newServers">The new servers to add.</param>
        private void AddServers(List<IServerModel> newServers)
        {
            if (null != newServers)
            {
                foreach (var s in newServers)
                {
                    if (!this.servers.ContainsKey(s.Name))
                    {
                        this.servers[s.Name] = new ServerViewModel(this.vmRoot, s);
                    }
                }
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Fired whenever a public property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the property change event. Maybe called from any thread - we always fire the
        /// event on the main UI thread.
        /// </summary>
        /// <param name="propertyName">The name of the property that changed.</param>
        private void OnPropertyChanged(string propertyName)
        {
            NotifyHelper.VerifyProperty(propertyName, this);
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged.Raise(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion // INotifyPropertyChanged Members

        #region IPage Members

        /// <summary>
        /// Used to notify the page when is made the active page, or when it has become inactive.
        /// </summary>
        /// <param name="isActive">True if this page is now active, false if it has become
        /// inactive.</param>
        public void NotifyActiveChanged(bool isActive)
        {
        }

        /// <summary>
        /// Notifies the active page that the physical back button has been pressed on the
        /// client device.
        /// </summary>
        public void NotifyBackButtonPressed()
        {
        }

        /// <summary>
        /// Used to access the back command that will navigate you from the current page to the
        /// previous page in the navigation history.
        /// </summary>
        public ViewModelManager VmManager
        {
            get { return this.vmRoot; }
        }

        #endregion
    }
}
